package system

import (
	"context"
	"fmt"
	"time"

	"admin_template/bff/admin/hertz_gen/base"
	genSysRule "admin_template/bff/admin/hertz_gen/system/sys_rule"
	"admin_template/bff/admin/internal/base/data"
	"admin_template/bff/admin/internal/entity/ent"
	"admin_template/bff/admin/internal/entity/ent/predicate"
	"admin_template/bff/admin/internal/entity/ent/sysrole"
	"admin_template/bff/admin/internal/entity/ent/sysrule"
	"admin_template/bff/admin/internal/service/system"

	"gitee.com/byx_darwin/go-tools/kitex/rpc_error"
	"github.com/pkg/errors"
)

type ruleRepo struct {
	*data.Data
	db *ent.SysRuleClient
}

func NewRuleRepo(d *data.Data) system.RuleRepo {
	return &ruleRepo{
		Data: d,
		db:   d.DB.SysRule,
	}
}

func (repo *ruleRepo) Add(ctx context.Context, req any) error {
	info, ok := req.(*genSysRule.AddRequest)
	if !ok {
		return rpc_error.NewBizStatusError(rpc_error.ErrorTypeInvalid,
			errors.New("req not *sysRule.AddRequest"))
	}
	query := repo.db.
		Create().
		SetTitle(info.Title).
		SetLocale(info.Locale).
		SetParentID(int(info.ParentId)).
		SetWeight(info.Weight).
		SetType(int8(info.RuleType)).
		SetIcon(info.Icon).
		SetRouteName(info.RouteName).
		SetRoutePath(info.RoutePath).
		SetComponent(info.Component).
		SetRedirect(info.Redirect).
		SetPermission(info.Permission).
		SetIsIframe(info.IsIframe).
		SetKeepalive(info.Keepalive).
		SetRequiresAuth(info.RequiresAuth).
		SetHideInMenu(info.HideInMenu).
		SetHideChildrenInMenu(info.HideChildrenInMenu).
		SetActiveMenu(info.ActiveMenu).
		SetNoAffix(info.NoAffix).
		SetDefault(info.Default)
	if info.ParentId != 0 {
		query = query.SetParentID(int(info.ParentId))
	}
	err := query.Exec(ctx)
	if err != nil {
		msg := fmt.Sprintf("sys rule create(title:%s )",
			info.Title)
		if ent.IsConstraintError(err) {
			err = errors.Wrap(err, msg)
			return rpc_error.NewBizStatusError(rpc_error.ErrorTypeDataRepeat, err)
		} else {
			err = errors.Wrap(err, msg)
			return rpc_error.NewBizStatusError(rpc_error.ErrorTypeSqlInsertFailure, err)
		}
	}
	return nil
}
func (repo *ruleRepo) UpdateInfo(ctx context.Context,
	req any) error {
	info, ok := req.(*genSysRule.UpdateRequest)
	if !ok {
		return rpc_error.NewBizStatusError(rpc_error.ErrorTypeInvalid,
			errors.New("req not *sysRule.UpdateRequest"))
	}
	query := repo.db.
		UpdateOneID(int(info.Id)).
		SetTitle(info.Title).
		SetLocale(info.Locale).
		SetWeight(info.Weight).
		SetType(int8(info.RuleType)).
		SetIcon(info.Icon).
		SetRouteName(info.RouteName).
		SetRoutePath(info.RoutePath).
		SetComponent(info.Component).
		SetRedirect(info.Redirect).
		SetPermission(info.Permission).
		SetIsIframe(info.IsIframe).
		SetKeepalive(info.Keepalive).
		SetRequiresAuth(info.RequiresAuth).
		SetHideInMenu(info.HideInMenu).
		SetHideChildrenInMenu(info.HideChildrenInMenu).
		SetActiveMenu(info.ActiveMenu).
		SetNoAffix(info.NoAffix).
		SetDefault(info.Default)
	if info.ParentId != 0 {
		query = query.SetParentID(int(info.ParentId))
	}
	err := query.Exec(ctx)
	if err != nil {
		msg := fmt.Sprintf("sys rule update(title:%s )",
			info.Title)
		if ent.IsConstraintError(err) {
			err = errors.Wrap(err, msg)
			return rpc_error.NewBizStatusError(rpc_error.ErrorTypeDataRepeat, err)
		} else {
			err = errors.Wrap(err, msg)
			return rpc_error.NewBizStatusError(rpc_error.ErrorTypeSqlInsertFailure, err)
		}
	}
	return nil
}
func (repo *ruleRepo) List(ctx context.Context,
	req any) (int64, int64, int64, []any, error) {
	info, ok := req.(*genSysRule.ListRequest)
	if !ok {
		return 0, 0, 0, nil, rpc_error.NewBizStatusError(rpc_error.ErrorTypeInvalid,
			errors.New("req not *genSysApi.ListRequest"))
	}
	predicates := make([]predicate.SysRule, 0)
	predicates = append(predicates, sysrule.Not(sysrule.HasParent()))
	switch info.DefaultType {
	case 1:
		predicates = append(predicates, sysrule.Default(true))
	case 2:
		predicates = append(predicates, sysrule.Default(false))

	}
	switch info.State {
	case 1:
		predicates = append(predicates, sysrule.DeleteTimeNotNil())
	case 2:
		predicates = append(predicates, sysrule.DeleteTimeIsNil())
	}

	sql := repo.db.Query()
	if len(predicates) > 0 {
		sql = sql.Where(predicates...)
	}
	total, err := sql.Count(ctx)
	if err != nil {
		err = errors.Wrap(err,
			fmt.Sprintf("分页查询规则总数失败: "))
		return 0, 0, 0, nil, rpc_error.NewBizStatusError(rpc_error.ErrorTypeSqlQueryFailure, err)
	}
	if total == 0 {
		return 0, 0, 0, nil, nil
	}
	offset := (info.Page - 1) * info.Size
	list, err := repo.sqlSelect(true).Where(predicates...).
		Offset(int(offset)).
		Limit(int(info.Size)).Order(ent.Asc(sysrule.FieldWeight)).All(ctx)
	if err != nil {
		err = errors.Wrap(err,
			fmt.Sprintf("分页查询规则列表失败: "))
		return 0, 0, 0, nil, rpc_error.NewBizStatusError(rpc_error.ErrorTypeSqlQueryFailure, err)
	}
	ruleList := make([]any, 0, len(list))
	for _, value := range list {
		ruleList = append(ruleList, repo.genInfo(value))
	}
	return int64(total), info.Page, info.Size, ruleList, nil
}

func (repo *ruleRepo) All(ctx context.Context,
	req any) ([]any, error) {
	info, ok := req.(*genSysRule.AllRequest)
	if !ok {
		return nil, rpc_error.NewBizStatusError(rpc_error.ErrorTypeInvalid,
			errors.New("req not *genSysApi.AllRequest"))
	}

	predicates := make([]predicate.SysRule, 0)
	predicates = append(predicates, sysrule.Not(sysrule.HasParent()))
	switch info.DefaultType {
	case 1:
		predicates = append(predicates, sysrule.Default(true))
	case 2:
		predicates = append(predicates, sysrule.Default(false))

	}
	switch info.State {
	case 1:
		predicates = append(predicates, sysrule.DeleteTimeNotNil())
	case 2:
		predicates = append(predicates, sysrule.DeleteTimeIsNil())
	}

	list, err := repo.sqlSelect(true).
		Where(predicates...).
		WithChildren(func(cq *ent.SysRuleQuery) {
			cq.Order(ent.Asc(sysrule.FieldWeight)) // 子菜单排序
		}).
		Order(ent.Asc(sysrule.FieldWeight)).
		All(ctx)
	if err != nil {
		err = errors.Wrap(err,
			fmt.Sprintf("查询全部规则列表失败: "))
		return nil, rpc_error.NewBizStatusError(rpc_error.ErrorTypeSqlQueryFailure, err)
	}
	ruleList := make([]any, 0, len(list))
	for _, value := range list {
		ruleList = append(ruleList, repo.genInfo(value))
	}
	return ruleList, nil

}

func (repo *ruleRepo) StateUpdate(ctx context.Context,
	info *base.StateUpdateRequest) error {
	switch info.State {
	case 0:
		err := repo.db.UpdateOneID(int(info.ID)).SetDeleteTime(time.Now()).Exec(ctx)
		if err != nil {
			err = errors.Wrap(err,
				fmt.Sprintf("更新规则状态失败(ID:%d): ", info.ID))
			return rpc_error.NewBizStatusError(rpc_error.ErrorTypeSqlUpdateFailure, err)
		}
	case 1:
		err := repo.db.UpdateOneID(int(info.ID)).ClearDeleteTime().Exec(ctx)
		if err != nil {
			err = errors.Wrap(err,
				fmt.Sprintf("更新规则状态失败(ID:%d): ", info.ID))
			return rpc_error.NewBizStatusError(rpc_error.ErrorTypeSqlUpdateFailure, err)
		}
	case 2:
		err := repo.db.DeleteOneID(int(info.ID)).Exec(ctx)
		if err != nil {
			err = errors.Wrap(err,
				fmt.Sprintf("删除规则失败(ID:%d): ", info.ID))
			return rpc_error.NewBizStatusError(rpc_error.ErrorTypeSqlDeleteFailure, err)
		}
	}
	return nil
}
func (repo *ruleRepo) One(ctx context.Context,
	id int) (any, error) {
	info, err := repo.sqlSelect(true).Where(sysrule.ID(id)).First(ctx)
	if err != nil {
		if ent.IsNotFound(err) {
			return nil, rpc_error.NewBizStatusError(rpc_error.ErrorTypeDataInvalid, err)
		}
		return nil, rpc_error.NewBizStatusError(rpc_error.ErrorTypeSqlQueryFailure, err)
	}
	return repo.genInfo(info), nil
}

func (repo *ruleRepo) ListByRoleID(ctx context.Context, roleID int64) ([]*genSysRule.Info, error) {
	list, err := repo.sqlSelect(false).Where(
		sysrule.Or(sysrule.HasRolesWith(sysrole.ID(int(roleID))), sysrule.Default(true))).All(ctx)
	if err != nil {
		err = errors.Wrap(err, "查询角色规则列表失败")
		return nil, rpc_error.NewBizStatusError(rpc_error.ErrorTypeSqlQueryFailure, err)
	}

	childrenList := make(map[int64][]*genSysRule.Info)
	parentLength := 0

	for _, value := range list {
		if value.Edges.Parent != nil {
			pid := int64(value.Edges.Parent.ID)
			childrenList[pid] = append(childrenList[pid], repo.genInfo(value))
		} else {
			parentLength++
		}
	}

	ruleList := make([]*genSysRule.Info, 0, parentLength)
	for _, value := range list {
		if value.Edges.Parent == nil {
			ruleInfo := repo.genInfo(value)
			if childs, ok := childrenList[int64(value.ID)]; ok {
				ruleInfo.Children = childs
			}
			ruleList = append(ruleList, ruleInfo)
		}
	}
	return ruleList, nil
}

func (repo *ruleRepo) sqlSelect(isChildren bool) *ent.SysRuleSelect {
	params := []string{
		sysrule.FieldID,
		sysrule.FieldTitle,
		sysrule.FieldLocale,
		sysrule.FieldWeight,
		sysrule.FieldType,
		sysrule.FieldIcon,
		sysrule.FieldRouteName,
		sysrule.FieldRoutePath,
		sysrule.FieldComponent,
		sysrule.FieldRedirect,
		sysrule.FieldPermission,
		sysrule.FieldIsIframe,
		sysrule.FieldKeepalive,
		sysrule.FieldRequiresAuth,
		sysrule.FieldHideInMenu,
		sysrule.FieldHideChildrenInMenu,
		sysrule.FieldActiveMenu,
		sysrule.FieldNoAffix,
		sysrule.FieldDefault,
		sysrule.FieldCreateTime,
		sysrule.FieldUpdateTime}
	query := repo.db.Query()
	if isChildren {
		query = query.WithChildren(func(query *ent.SysRuleQuery) {
			query.Select(params...)
		})
	}
	query = query.WithParent(func(query *ent.SysRuleQuery) {
		query.Select(sysrule.FieldID)
	})
	return query.Select(params...)
}

func (repo *ruleRepo) genInfo(info *ent.SysRule) *genSysRule.Info {
	list := make([]*genSysRule.Info, 0, len(info.Edges.Children))
	for _, value := range info.Edges.Children {
		list = append(list, repo.genInfo(value))
	}
	state := int64(1)
	if info.DeleteTime.Unix() > 0 {
		state = 0
	}
	parentId := int64(0)
	if info.Edges.Parent != nil {
		parentId = int64(info.Edges.Parent.ID)
	}

	return &genSysRule.Info{
		Id:                 int64(info.ID),
		Title:              info.Title,
		Locale:             info.Locale,
		Weight:             info.Weight,
		RuleType:           genSysRule.RuleType(info.Type),
		Icon:               info.Icon,
		RouteName:          info.RouteName,
		RoutePath:          info.RoutePath,
		Component:          info.Component,
		Redirect:           info.Redirect,
		Permission:         info.Permission,
		Keepalive:          info.Keepalive,
		IsIframe:           info.IsIframe,
		RequiresAuth:       info.RequiresAuth,
		HideInMenu:         info.HideInMenu,
		HideChildrenInMenu: info.HideChildrenInMenu,
		ActiveMenu:         info.ActiveMenu,
		NoAffix:            info.NoAffix,
		Default:            info.Default,
		State:              state,
		Children:           list,
		ParentId:           parentId,
	}
}
func (repo *ruleRepo) Init(ctx context.Context) error {
	list := make([]*genSysRule.AddRequest, 0, 7)
	list = append(list, &genSysRule.AddRequest{
		Title:     "概览",
		Locale:    "menu.dashboard",
		Weight:    1,
		RuleType:  genSysRule.RuleType_MENU,
		Icon:      "",
		RouteName: "dashboard",
		RoutePath: "dashboard",
		Component: "/views/main/dashboard/workplace/index.vue",
		Default:   true,
		ParentId:  0,
	})
	list = append(list, &genSysRule.AddRequest{
		Title:     "系统管理",
		Locale:    "menu.systemManage",
		Weight:    2,
		RuleType:  genSysRule.RuleType_DIR,
		Icon:      "",
		RouteName: "systemManage",
		RoutePath: "systemManage",
		Component: "/views/main/systemManage/index.vue",
		Default:   false,
		ParentId:  0,
	})
	list = append(list, &genSysRule.AddRequest{
		Title:     "API",
		Locale:    "menu.systemManage.api",
		Weight:    1,
		RuleType:  genSysRule.RuleType_MENU,
		Icon:      "",
		RouteName: "api",
		RoutePath: "api",
		Component: "/views/main/systemManage/api/index.vue",
		Default:   false,
		ParentId:  2,
	})
	list = append(list, &genSysRule.AddRequest{
		Title:     "菜单",
		Locale:    "menu.systemManage.rule",
		Weight:    2,
		RuleType:  genSysRule.RuleType_MENU,
		Icon:      "",
		RouteName: "rule",
		RoutePath: "rule",
		Component: "/views/main/systemManage/rule/index.vue",
		Default:   false,
		ParentId:  2,
	})
	list = append(list, &genSysRule.AddRequest{
		Title:     "角色",
		Locale:    "menu.systemManage.role",
		Weight:    3,
		RuleType:  genSysRule.RuleType_MENU,
		Icon:      "",
		RouteName: "role",
		RoutePath: "role",
		Component: "/views/main/systemManage/role/index.vue",
		Default:   false,
		ParentId:  2,
	})
	list = append(list, &genSysRule.AddRequest{
		Title:     "公司",
		Locale:    "menu.systemManage.company",
		Weight:    4,
		RuleType:  genSysRule.RuleType_MENU,
		Icon:      "",
		RouteName: "company",
		RoutePath: "company",
		Component: "/views/main/systemManage/company/index.vue",
		Default:   false,
		ParentId:  2,
	})
	list = append(list, &genSysRule.AddRequest{
		Title:     "用户",
		Locale:    "menu.systemManage.user",
		Weight:    5,
		RuleType:  genSysRule.RuleType_MENU,
		Icon:      "",
		RouteName: "user",
		RoutePath: "user",
		Component: "/views/main/systemManage/user/index.vue",
		Default:   false,
		ParentId:  2,
	})

	var err error
	tx, err := repo.DB.Tx(ctx)
	if err != nil {
		return err
	}
	defer func() {
		if err != nil {
			_ = tx.Rollback()
		}
	}()
	for _, info := range list {
		query := tx.SysRule.Create().
			SetTitle(info.Title).
			SetLocale(info.Locale).
			SetWeight(info.Weight).
			SetType(int8(info.RuleType)).
			SetIcon(info.Icon).
			SetRouteName(info.RouteName).
			SetRoutePath(info.RoutePath).
			SetComponent(info.Component).
			SetDefault(info.Default)
		if info.ParentId != 0 {
			query = query.SetParentID(int(info.ParentId))
		}

		err = query.Exec(ctx)
		if err != nil {
			return err
		}
	}
	err = tx.Commit()
	if err != nil {
		return err
	}
	return nil
}
